/*
 *     Copyright 2006 Robert Burrell Donkin
 *  
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *  
 *   http://www.apache.org/licenses/LICENSE-2.0
 *  
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package rat.element;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;

import rat.element.comparator.ComparatorFactory;
import rat.license.ILicenseMatcher;

/**
 * Contains elements.
 */
public class Directory extends AbstractElement {
	
	private final List elements;
	
	private boolean filled;

	private final ILicenseMatcher matcher;
    private final Pattern ignoreNameRegex;
	
	public Directory(File file, final ILicenseMatcher matcher) {
        this(file, matcher, null);
	}
	
    public Directory(File file, final ILicenseMatcher matcher, final Pattern ignoreNameRegex) {
        super(file);
        this.elements = new ArrayList();
        this.matcher = matcher;
        this.ignoreNameRegex = ignoreNameRegex;
    }
    
	public Collection getElements() {
		return elements;
	}
	
    public boolean isRestricted() {
        return false;
    }
    
	/**
	 * Fills child elements with values.
	 */
	public void fill() {
		elements.clear();
		final File[] files = file.listFiles();
        if (files != null) {
            for (int i = 0; i < files.length; i++) {
                final File file = files[i];
                final String name = file.getName();
                if (!ignored(name)) {
                    IElement element = null;
                    if (file.isDirectory()) {
                        if (isRestricted(file)) {
                            element = new RestrictedDirectory(file, matcher, ignoreNameRegex);
                        } else {
                            element = new Directory(file, matcher, ignoreNameRegex);
                        }
                    } else if (isNote(file)) {
                        element = new NoteElement(file);
                    } else if (isArchive(file)) {
                        element = new ArchiveElement(file);
                    } else if (isBinary(file)) {
                        element = new BinaryElement(file);
                    }
    
                    if (element == null) {
                        element = new StandardContentElement(file, matcher);
                    }
                    elements.add(element);
                }
            }
            Collections.sort(elements, ComparatorFactory
                    .createDirectoryLastComparator());
        }
        filled = true;
	}
    
    private boolean ignored(final String name) {
        boolean result = false;
        if (ignoreNameRegex != null) {
            result = ignoreNameRegex.matcher(name).matches();
        }
        return result;
    }
	
	public boolean isFilled() {
		return filled;
	}


	/**
	 * Guesses whether the given file is a note.
	 * @param file <code>File</code>, not null 
	 * @return true if the file is a note, false otherwise
	 */
	boolean isNote(final File file) {
		final String name = file.getName();
		return isNote(name);
	}

    boolean isNote(final String name) {
		boolean result = "NOTICE".equals(name) || "LICENSE".equals(name) || 		
				"LICENSE.txt".equals(name) || "NOTICE.txt".equals(name) || 
                "INSTALL".equalsIgnoreCase(name) || "INSTALL.txt".equalsIgnoreCase(name) ||
				"README".equalsIgnoreCase(name) || "README.txt".equalsIgnoreCase(name) || 
                "NEWS".equalsIgnoreCase(name) || "NEWS.txt".equalsIgnoreCase(name) ||
                "AUTHOR".equalsIgnoreCase(name) || "AUTHOR.txt".equalsIgnoreCase(name) ||
                "AUTHORS".equalsIgnoreCase(name) || "AUTHORS.txt".equalsIgnoreCase(name) ||
                "ChangeLog".equalsIgnoreCase(name) || "ChangeLog.txt".equalsIgnoreCase(name) ||
				"DISCLAIMER".equals(name) || "DISCLAIMER.txt".equals(name) ||
				"KEYS".equals(name) || "KEYS.txt".equals(name) ||
                "RELEASE-NOTES".equals(name) || "RELEASE-NOTES.txt".equals(name) ||
                "RELEASE_NOTES".equals(name) || "RELEASE_NOTES.txt".equals(name) ||
                "UPGRADE".equals(name) || "UPGRADE.txt".equals(name) ||
                "STATUS".equals(name) || "STATUS.txt".equals(name) ||
                "THIRD_PARTY_NOTICES".equals(name) || "THIRD_PARTY_NOTICES.txt".equals(name) ||
                "COPYRIGHT".equals(name) || "COPYRIGHT.txt".equals(name) ||
                "BUILDING".equals(name) || "BUILDING.txt".equals(name);
		if (!result && name != null) {
			result = name.endsWith("LICENSE") || name.endsWith("LICENSE.txt")
			|| name.endsWith("NOTICE") || name.endsWith("NOTICE.txt");
		}
		return result;
	} 

	/**
	 * Guesses whether the given file is binary.
     * Note that binary is used in a broad but technical sense:
     * files with structure contents which cannot be commented.
	 * @param file <code>File</code>, not null 
	 * @return true if the file is likely to be binary, false otherwise
	 */
	boolean isBinary(final File file) {
		final String name = file.getName();
		return isBinary(name);
	}

	boolean isBinary(final String name) {
		final String suffix = suffix(name);
		return isImage(suffix) || isKeystore(suffix) ||
		isBytecode(suffix) || isBinaryData(suffix) ||
        isExecutable(suffix, name) ||
        "MANIFEST.MF".equals(name);
	}
	
	boolean isBinaryData(final String suffix) {
		return "dat".equalsIgnoreCase(suffix) || "doc".equalsIgnoreCase(suffix)
        || "ncb".equalsIgnoreCase(suffix) || "idb".equalsIgnoreCase(suffix)
        || "suo".equalsIgnoreCase(suffix) || "xcf".equalsIgnoreCase(suffix)
        || "raj".equalsIgnoreCase(suffix) || "cert".equalsIgnoreCase(suffix)
        || "ks".equalsIgnoreCase(suffix) || "ts".equalsIgnoreCase(suffix);
	}
    
    boolean isExecutable(final String suffix, final String name) {
        return "exe".equalsIgnoreCase(suffix) || "dll".equalsIgnoreCase(suffix)
        || "lib".equalsIgnoreCase(suffix) || "so".equalsIgnoreCase(suffix)
        || "a".equalsIgnoreCase(suffix) || name.indexOf(".so.") >= 0 
        || name.equals("java") || "exp".equalsIgnoreCase(suffix)
        || name.indexOf(".dll.") >= 0;
    }
	
	boolean isKeystore(final String suffix) {
		return "jks".equalsIgnoreCase(suffix) || "keystore".equalsIgnoreCase(suffix);
	}
	
	boolean isImage(final String suffix) {
		return "png".equalsIgnoreCase(suffix) || "pdf".equalsIgnoreCase(suffix) ||
		"gif".equalsIgnoreCase(suffix) || "giff".equalsIgnoreCase(suffix) ||
		"tif".equalsIgnoreCase(suffix) || "tiff".equalsIgnoreCase(suffix) ||
		"jpg".equalsIgnoreCase(suffix) || "jpeg".equalsIgnoreCase(suffix) ||
        "ico".equalsIgnoreCase(suffix);
	}
	
	boolean isArchive(final File file) {
		final String name = file.getName();
		return isArchive(name);
	}

	boolean isBytecode(final String suffix) {
		return "class".equalsIgnoreCase(suffix) || "pyd".equalsIgnoreCase(suffix)
        || "obj".equalsIgnoreCase(suffix) || "pyc".equalsIgnoreCase(suffix);
	}
	
	boolean isArchive(final String name) {
		final String suffix = suffix(name);
		return "jar".equalsIgnoreCase(suffix) || "gz".equalsIgnoreCase(suffix) ||
		"zip".equalsIgnoreCase(suffix) || "tar".equalsIgnoreCase(suffix) ||
		"bz".equalsIgnoreCase(suffix) || "bz2".equalsIgnoreCase(suffix) ||
        "rar".equalsIgnoreCase(suffix) || "war".equalsIgnoreCase(suffix);
	}
	
	String suffix(final String str) {
		String result = "";
		final int lastDot = str.lastIndexOf('.');
		final int length = str.length();
		if (lastDot >= 0  && lastDot < length) {
			result = str.substring(lastDot + 1, length);
		}
		return result;
	}
	
	boolean isRestricted(File file) {
		String name = file.getName();
		boolean result = name.startsWith(".");
		return result;
	}
	
	public void accept(IElementVisitor visitor) {
		visitor.visit(this);
	}
	
	public String toString() {
		return prefix() + getPath();
	}

	protected String prefix() {
		return "D       ";
	}
}
